﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Ors.RInterface.Engine.PInvoke;
using System.Runtime.InteropServices;

namespace Ors.RInterface.Engine
{
    class VarAccessor:IVarAccessor
    {
        IntPtr env;
        string name;
        REngine engine;
        public VarAccessor(string name,IntPtr env,REngine engine)
        {
            this.env = env;
            this.name = name;
            this.engine = engine;
        }
        private void CheckIfUnbound(IntPtr p,int free,string name)
        {
            if (p == engine.GetUnboundValue())
            {
                R.Rf_unprotect(free);
                throw new Exception(string.Format("Symbol {0} is not defined.",name));
            }
        }
        private void CheckIfScalar(int len, int free, string name)
        {
            if (len != 1)
            {
                R.Rf_unprotect(free);
                throw new Exception(string.Format("{0} is not a scalar value.", name));
            }
        }
        #region IVarAccessor Members

        public double[] AsRealArray()
        {
            double[] res;
            IntPtr pname = R.Rf_install(name);
            R.Rf_protect(pname);
            IntPtr var = R.Rf_findVar(pname, env);
            R.Rf_protect(var);
            CheckIfUnbound(var, 2, name);
            if (!R.Rf_isReal(var))
                ThrowNonCompatible(2);
            int len = R.Rf_length(var);
            res = new double[len];
            Marshal.Copy(R.REAL(var), res, 0, len);
            R.Rf_unprotect(2);
            return res;
        }

        public double AsReal()
        {
            double[] res;
            IntPtr pname = R.Rf_install(name);
            R.Rf_protect(pname);
            IntPtr var = R.Rf_findVar(pname, env);
            R.Rf_protect(var);
            CheckIfUnbound(var, 2, name);
            if (!R.Rf_isReal(var))
                ThrowNonCompatible(2);
            int len = R.Rf_length(var);
            CheckIfScalar(len,2,name);
            res = new double[len];
            Marshal.Copy(R.REAL(var), res, 0, len);
            R.Rf_unprotect(2);
            return res[0];
        }

        public int[] AsIntegerArray()
        {
            int[] res;
            IntPtr pname = R.Rf_install(name);
            R.Rf_protect(pname);
            IntPtr var = R.Rf_findVar(pname, env);
            R.Rf_protect(var);
            CheckIfUnbound(var, 2, name);
            if (!R.Rf_isInteger(var))
                ThrowNonCompatible(2);
            int len = R.Rf_length(var);
            res = new int[len];
            Marshal.Copy(R.INTEGER(var), res, 0, len);
            R.Rf_unprotect(2);
            return res;
        }

        public int AsInteger()
        {
            int[] res;
            IntPtr pname = R.Rf_install(name);
            R.Rf_protect(pname);
            IntPtr var = R.Rf_findVar(pname, env);
            R.Rf_protect(var);
            CheckIfUnbound(var, 2, name);
            if (!R.Rf_isInteger(var))
                ThrowNonCompatible(2);
            int len = R.Rf_length(var);
            CheckIfScalar(len, 2, name);
            res = new int[len];
            Marshal.Copy(R.INTEGER(var), res, 0, len);
            R.Rf_unprotect(2);
            return res[0];
        }

        public bool[] AsLogicalArray()
        {
            int[] res;
            IntPtr pname = R.Rf_install(name);
            R.Rf_protect(pname);
            IntPtr var = R.Rf_findVar(pname, env);
            R.Rf_protect(var);
            CheckIfUnbound(var, 2, name);
            if (!R.Rf_isLogical(var))
                ThrowNonCompatible(2);
            int len = R.Rf_length(var);
            res = new int[len];
            Marshal.Copy(R.INTEGER(var), res, 0, len);
            R.Rf_unprotect(2);
            return res.Select(i=>i!=0).ToArray();
        }

        public bool AsLogicalScalar()
        {
            int[] res;
            IntPtr pname = R.Rf_install(name);
            R.Rf_protect(pname);
            IntPtr var = R.Rf_findVar(pname, env);
            R.Rf_protect(var);
            CheckIfUnbound(var, 2, name);
            if (!R.Rf_isLogical(var))
                ThrowNonCompatible(2);
            int len = R.Rf_length(var);
            CheckIfScalar(len, 2, name);
            res = new int[len];
            Marshal.Copy(R.INTEGER(var), res, 0, len);
            R.Rf_unprotect(2);
            return res[0]!=0;
        }

        public string[] AsStringArray()
        {
            string[] res;
            IntPtr pname = R.Rf_install(name);
            R.Rf_protect(pname);
            IntPtr var = R.Rf_findVar(pname, env);
            R.Rf_protect(var);
            CheckIfUnbound(var, 2, name);
            if (!R.Rf_isString(var))
                ThrowNonCompatible(2);
            int len = R.Rf_length(var);
            res = new string[len];
            for (int i = 0; i < res.Length; ++i)
            {
                res[i] = R.Rf_translateChar(R.STRING_ELT(var, i));
            }
            R.Rf_unprotect(2);
            return res;
        }

        public string AsString()
        {
            string[] res;
            IntPtr pname = R.Rf_install(name);
            R.Rf_protect(pname);
            IntPtr var = R.Rf_findVar(pname, env);
            R.Rf_protect(var);
            CheckIfUnbound(var, 2, name);
            if (!R.Rf_isString(var))
                ThrowNonCompatible(2);
            int len = R.Rf_length(var);
            CheckIfScalar(len, 2, name);
            res = new string[len];
            res[0] = R.Rf_translateChar(R.STRING_ELT(var, 0));
            R.Rf_unprotect(2);
            return res[0];
        }

        public DateTime[] AsDateTimeArray()
        {
            double[] res;
            IntPtr pname = R.Rf_install(name);
            R.Rf_protect(pname);
            IntPtr var = R.Rf_findVar(pname, env);
            R.Rf_protect(var);
            CheckIfUnbound(var, 2, name);
            if (!CheckIfDateTime(var))
                ThrowNonCompatible(2);
            int len = R.Rf_length(var);
            res = new double[len];
            Marshal.Copy(R.REAL(var), res, 0, len);
            R.Rf_unprotect(2);
            return res.Select(q=>engine.FromUnix(q)).ToArray();
        }

        

        public DateTime AsDateTime()
        {
            double[] res;
            IntPtr pname = R.Rf_install(name);
            R.Rf_protect(pname);
            IntPtr var = R.Rf_findVar(pname, env);
            R.Rf_protect(var);
            CheckIfUnbound(var, 2, name);
            if (!CheckIfDateTime(var))
                ThrowNonCompatible(2);
            int len = R.Rf_length(var);
            CheckIfScalar(len, 2, name);
            res = new double[len];
            Marshal.Copy(R.REAL(var), res, 0, len);
            R.Rf_unprotect(2);
            return engine.FromUnix(res[0]);
        }
        public IEnumerable<object[]> AsDataFrame(out Array colNames,out Array rowNames)
        {
            List<object[]> res;
            IntPtr pname = R.Rf_install(name);
            R.Rf_protect(pname);
            IntPtr var = R.Rf_findVar(pname, env);
            R.Rf_protect(var);
            CheckIfUnbound(var, 2, name);
            if (!CheckIfFrame(var))
                ThrowNonCompatible(2);
            int cols = R.Rf_length(var);
            res = new List<object[]>();
            List<Array> columnItems = new List<Array>();
            int rows = 0;
            for (int i = 0; i < cols; ++i)
            {
                IntPtr col = R.VECTOR_ELT(var, i);
                R.Rf_protect(col);
                rows = R.Rf_length(col);
                if (i == 0)
                {
                    res = MakeList(rows, cols);
                }
                columnItems.Add(engine.ToNetArray(col));
                R.Rf_unprotect(1);
            }
            for (int i = 0; i < rows; ++i)
            {
                for (int j = 0; j < cols; ++j)
                {
                    Array inner = columnItems[j];
                    if (null != inner)
                    {
                        res[i][j] = inner.GetValue(i);
                    }
                }
            }

            rowNames = engine.GetRowNames(var);
            colNames = engine.GetNames(var);

            R.Rf_unprotect(2);
            return res;
        }
       

        public double[][] AsMatrixOfReal()
        {
            double[] res;
            IntPtr pname = R.Rf_install(name);
            R.Rf_protect(pname);
            IntPtr var = R.Rf_findVar(pname, env);
            R.Rf_protect(var);
            CheckIfUnbound(var, 2, name);
            if (!R.Rf_isReal(var))
                ThrowNonCompatible(2);
           
            int[] dims = engine.GetMatrixDimensions(var);
            if (dims.Length != 2)
            {
                ThrowNonCompatible(2, "dim attribute does not appear to be properly set for a rectangular matrix");
            }

            int len = R.Rf_length(var);
            res = new double[len];
            Marshal.Copy(R.REAL(var), res, 0, len);

            double[][] mtx = CreateRectangularMatrix(res,dims);

            R.Rf_unprotect(2);
            return mtx;
        }

       

       

        public int[][] AsMatrixOfInteger()
        {
            int[] res;
            IntPtr pname = R.Rf_install(name);
            R.Rf_protect(pname);
            IntPtr var = R.Rf_findVar(pname, env);
            R.Rf_protect(var);
            CheckIfUnbound(var, 2, name);
            if (!R.Rf_isInteger(var))
                ThrowNonCompatible(2);

            int[] dims = engine.GetMatrixDimensions(var);
            if (dims.Length != 2)
            {
                ThrowNonCompatible(2, "dim attribute does not appear to be properly set for a rectangular matrix");
            }

            int len = R.Rf_length(var);
            res = new int[len];
            Marshal.Copy(R.INTEGER(var), res, 0, len);

            int[][] mtx = CreateRectangularMatrix(res, dims);

            R.Rf_unprotect(2);
            return mtx;
        }

        
        #endregion
        private List<object[]> MakeList(int rows, int cols)
        {
            List<object[]> l = new List<object[]>(rows);
            for (int i = 0; i < rows; ++i)
            {
                l.Add(new object[cols]);
            }
            return l;
        }
        private bool CheckIfFrame(IntPtr var)
        {
            string[] atts = engine.GetClass(var);
            if (atts.Length != 1)
                return false;
            return atts[0] == "data.frame";
        }
        private bool CheckIfDateTime(IntPtr var)
        {
            return engine.IfDateTime(var);
        }
        private void ThrowNonCompatible(int unprotect)
        {
            R.Rf_unprotect(unprotect);
            throw new InvalidOperationException("Type not compatible.");
        }
        private void ThrowNonCompatible(int unprotect, string additional)
        {
            R.Rf_unprotect(unprotect);
            throw new InvalidOperationException("Type not compatible:"+additional);
        }

        private T[][] CreateRectangularMatrix<T>(T[] res, int[] dims)
        {
            T[][] mtx = new T[dims[1]][];
            for (int i = 0; i < mtx.Length;++i  )
                mtx[i] = new T[dims[0]];
            for (int i = 0; i < dims[1]; ++i)
            {
                for (int j = 0; j < dims[0]; ++j)
                {
                    mtx[i][j] = res[i * dims[0] + j];
                }
            }
            return mtx;
        }
        
    }
}
